/*
* (C) 2006,2011,2012,2014,2015 Jack Lloyd
* (C) 2022 René Meusel, Rohde & Schwarz Cybersecurity
*
* Botan is released under the Simplified BSD License (see license.txt)
*/

#include "tests.h"

#if defined(BOTAN_HAS_X509_CERTIFICATES)
   #include <botan/data_src.h>
   #include <botan/exceptn.h>
   #include <botan/pk_keys.h>
   #include <botan/pkcs10.h>
   #include <botan/x509_crl.h>
   #include <botan/x509path.h>
   #include <botan/internal/calendar.h>
   #include <botan/internal/filesystem.h>
   #include <botan/internal/fmt.h>
   #include <botan/internal/parsing.h>

   #if defined(BOTAN_HAS_ECDSA)
      #include <botan/ec_group.h>
   #endif

   #include <algorithm>
   #include <fstream>
   #include <limits>
   #include <map>
   #include <string>
   #include <vector>
#endif

namespace Botan_Tests {

namespace {

#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)

   #if defined(BOTAN_HAS_RSA) && defined(BOTAN_HAS_EMSA_PKCS1)

std::map<std::string, std::string> read_results(const std::string& results_file, const char delim = ':') {
   std::ifstream in(results_file);
   if(!in.good()) {
      throw Test_Error("Failed reading " + results_file);
   }

   std::map<std::string, std::string> m;
   std::string line;
   while(in.good()) {
      std::getline(in, line);
      if(line.empty()) {
         continue;
      }
      if(line[0] == '#') {
         continue;
      }

      std::vector<std::string> parts = Botan::split_on(line, delim);

      if(parts.size() != 2) {
         throw Test_Error("Invalid line " + line);
      }

      m[parts[0]] = parts[1];
   }

   return m;
}

std::vector<Botan::X509_Certificate> load_cert_file(const std::string& filename) {
   Botan::DataSource_Stream in(filename);

   std::vector<Botan::X509_Certificate> certs;
   while(!in.end_of_data()) {
      try {
         certs.emplace_back(in);
      } catch(Botan::Decoding_Error&) {}
   }

   return certs;
}

std::set<Botan::Certificate_Status_Code> flatten(const Botan::CertificatePathStatusCodes& codes) {
   std::set<Botan::Certificate_Status_Code> result;

   for(const auto& statuses : codes) {
      result.insert(statuses.begin(), statuses.end());
   }

   return result;
}

Botan::Path_Validation_Restrictions get_default_restrictions(bool req_revocation_info, bool ocsp_all_intermediates) {
   return Botan::Path_Validation_Restrictions(req_revocation_info, 80 /*some tests use SHA-1*/, ocsp_all_intermediates);
}

Botan::Path_Validation_Restrictions get_allow_non_self_signed_anchors_restrictions(bool req_revocation_info,
                                                                                   bool ocsp_all_intermediates) {
   return Botan::Path_Validation_Restrictions(req_revocation_info,
                                              80, /*some tests use SHA-1*/
                                              ocsp_all_intermediates,
                                              std::chrono::seconds::zero(),
                                              std::make_unique<Botan::Certificate_Store_In_Memory>(),
                                              false,
                                              false /* require_self_signed_trust_anchors */);
}

std::vector<Botan::Path_Validation_Restrictions> restrictions_to_test(bool req_revocation_info,
                                                                      bool ocsp_all_intermediates) {
   std::vector<Botan::Path_Validation_Restrictions> restrictions;
   restrictions.emplace_back(get_default_restrictions(req_revocation_info, ocsp_all_intermediates));
   restrictions.emplace_back(
      get_allow_non_self_signed_anchors_restrictions(req_revocation_info, ocsp_all_intermediates));
   return restrictions;
}

class X509test_Path_Validation_Tests final : public Test {
   public:
      std::vector<Test::Result> run() override {
         std::vector<Test::Result> results;
         for(const auto& restrictions : restrictions_to_test(false, false)) {
            auto partial_res = run_with_restrictions(restrictions);
            results.insert(results.end(), partial_res.begin(), partial_res.end());
         }
         return results;
      }

   private:
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions) {
         std::vector<Test::Result> results;

         // Test certs generated by https://github.com/yymax/x509test
         Botan::X509_Certificate root(Test::data_file("x509/x509test/root.pem"));
         Botan::Certificate_Store_In_Memory trusted;
         trusted.add_certificate(root);

         auto validation_time = Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();

         for(const auto& [filename, expected_result] : read_results(Test::data_file("x509/x509test/expected.txt"))) {
            Test::Result result("X509test path validation");
            result.start_timer();

            std::vector<Botan::X509_Certificate> certs = load_cert_file(Test::data_file("x509/x509test/" + filename));

            if(certs.empty()) {
               throw Test_Error("Failed to read certs from " + filename);
            }

            Botan::Path_Validation_Result path_result = Botan::x509_path_validate(
               certs, restrictions, trusted, "www.tls.test", Botan::Usage_Type::TLS_SERVER_AUTH, validation_time);

            if(path_result.successful_validation() && path_result.trust_root() != root) {
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
            }

            result.test_eq("test " + filename, path_result.result_string(), expected_result);
            result.test_eq("test no warnings string", path_result.warnings_string(), "");
            result.confirm("test no warnings", path_result.no_warnings());
            result.end_timer();
            results.push_back(result);
         }

         // test softfail
         {
            Test::Result result("X509test path validation softfail");
            result.start_timer();

            // this certificate must not have a OCSP URL
            const std::string filename = "ValidAltName.pem";
            std::vector<Botan::X509_Certificate> certs = load_cert_file(Test::data_file("x509/x509test/" + filename));
            if(certs.empty()) {
               throw Test_Error("Failed to read certs from " + filename);
            }

            Botan::Path_Validation_Result path_result =
               Botan::x509_path_validate(certs,
                                         restrictions,
                                         trusted,
                                         "www.tls.test",
                                         Botan::Usage_Type::TLS_SERVER_AUTH,
                                         validation_time,
                                         /* activate check_ocsp_online */ std::chrono::milliseconds(1000),
                                         {});

            if(path_result.successful_validation() && path_result.trust_root() != root) {
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
            }

            // certificate verification succeed even if no OCSP URL (softfail)
            result.confirm("test success", path_result.successful_validation());
            result.test_eq("test " + filename, path_result.result_string(), "Verified");
      #if defined(BOTAN_TARGET_OS_HAS_THREADS) && defined(BOTAN_HAS_HTTP_UTIL)
            // if softfail, there is warnings
            result.confirm("test warnings", !path_result.no_warnings());
            result.test_eq("test warnings string", path_result.warnings_string(), "[0] OCSP URL not available");
      #endif
            result.end_timer();
            results.push_back(result);
         }

         return results;
      }
};

BOTAN_REGISTER_TEST("x509", "x509_path_x509test", X509test_Path_Validation_Tests);

class NIST_Path_Validation_Tests final : public Test {
   public:
      std::vector<Test::Result> run() override;

   private:
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
};

std::vector<Test::Result> NIST_Path_Validation_Tests::run() {
   std::vector<Test::Result> results;
   for(const auto& restrictions : restrictions_to_test(true, true)) {
      auto partial_res = run_with_restrictions(restrictions);
      results.insert(results.end(), partial_res.begin(), partial_res.end());
   }
   return results;
}

std::vector<Test::Result> NIST_Path_Validation_Tests::run_with_restrictions(
   const Botan::Path_Validation_Restrictions& restrictions) {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("NIST path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   /**
   * Code to run the X.509v3 processing tests described in "Conformance
   *  Testing of Relying Party Client Certificate Path Processing Logic",
   *  which is available on NIST's web site.
   *
   * https://csrc.nist.gov/projects/pki-testing/x-509-path-validation-test-suite
   *
   * Known Failures/Problems:
   *  - Policy extensions are not implemented, so we skip tests #34-#53.
   *  - Tests #75 and #76 are skipped as they make use of relatively
   *    obscure CRL extensions which are not supported.
   */
   std::map<std::string, std::string> expected = read_results(Test::data_file("x509/nist/expected.txt"));

   const Botan::X509_Certificate root_cert(Test::data_file("x509/nist/root.crt"));
   const Botan::X509_CRL root_crl(Test::data_file("x509/nist/root.crl"));

   const auto validation_time = Botan::calendar_point(2018, 4, 1, 9, 30, 33).to_std_timepoint();

   for(const auto& [test_name, expected_result] : expected) {
      Test::Result result("NIST path validation");
      result.start_timer();

      try {
         const auto all_files = Test::files_in_data_dir("x509/nist/" + test_name);

         Botan::Certificate_Store_In_Memory store;
         store.add_certificate(root_cert);
         store.add_crl(root_crl);

         std::vector<Botan::X509_Certificate> end_certs = {
            Botan::X509_Certificate(Test::data_file("x509/nist/" + test_name + "/end.crt"))};

         for(const auto& file : all_files) {
            if(file.ends_with(".crt") && file != "end.crt") {
               end_certs.emplace_back(file);
            } else if(file.ends_with(".crl")) {
               Botan::DataSource_Stream in(file, true);
               Botan::X509_CRL crl(in);
               store.add_crl(crl);
            }
         }

         Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
            end_certs, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
      } catch(std::exception& e) {
         result.test_failure(test_name, e.what());
      }

      result.end_timer();
      results.push_back(result);
   }

   return results;
}

BOTAN_REGISTER_TEST("x509", "x509_path_nist", NIST_Path_Validation_Tests);

class Extended_Path_Validation_Tests final : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> Extended_Path_Validation_Tests::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("Extended x509 path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   auto validation_time = Botan::calendar_point(2017, 9, 1, 9, 30, 33).to_std_timepoint();

   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/extended/expected.txt"))) {
      Test::Result result("Extended X509 path validation");
      result.start_timer();

      const auto all_files = Test::files_in_data_dir("x509/extended/" + test_name);

      Botan::Certificate_Store_In_Memory store;

      for(const auto& file : all_files) {
         if(file.ends_with(".crt") && file != "end.crt") {
            store.add_certificate(Botan::X509_Certificate(file));
         }
      }

      Botan::X509_Certificate end_user(Test::data_file("x509/extended/" + test_name + "/end.crt"));

      Botan::Path_Validation_Restrictions restrictions;
      Botan::Path_Validation_Result validation_result =
         Botan::x509_path_validate(end_user, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

      result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);

      result.end_timer();
      results.push_back(result);
   }

   return results;
}

BOTAN_REGISTER_TEST("x509", "x509_path_extended", Extended_Path_Validation_Tests);

      #if defined(BOTAN_HAS_PSS)

class PSS_Path_Validation_Tests : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> PSS_Path_Validation_Tests::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("RSA-PSS X509 signature validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   const auto validation_times = read_results(Test::data_file("x509/pss_certs/validation_times.txt"));

   auto validation_times_iter = validation_times.begin();

   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/pss_certs/expected.txt"))) {
      Test::Result result("RSA-PSS X509 signature validation");
      result.start_timer();

      const auto all_files = Test::files_in_data_dir("x509/pss_certs/" + test_name);

      std::optional<Botan::X509_CRL> crl;
      std::optional<Botan::X509_Certificate> end;
      std::optional<Botan::X509_Certificate> root;
      Botan::Certificate_Store_In_Memory store;
      std::optional<Botan::PKCS10_Request> csr;

      const auto validation_year = Botan::to_u32bit((validation_times_iter++)->second);

      const auto validation_time = Botan::calendar_point(validation_year, 0, 0, 0, 0, 0).to_std_timepoint();

      for(const auto& file : all_files) {
         if(file.find("end.crt") != std::string::npos) {
            end = Botan::X509_Certificate(file);
         } else if(file.find("root.crt") != std::string::npos) {
            root = Botan::X509_Certificate(file);
            store.add_certificate(*root);
         } else if(file.ends_with(".crl")) {
            crl = Botan::X509_CRL(file);
         } else if(file.ends_with(".csr")) {
            csr = Botan::PKCS10_Request(file);
         }
      }

      if(end && crl && root) {
         // CRL test
         const std::vector<Botan::X509_Certificate> cert_path = {*end, *root};
         const std::vector<std::optional<Botan::X509_CRL>> crls = {crl};
         auto crl_status = Botan::PKIX::check_crl(
            cert_path,
            crls,
            validation_time);  // alternatively we could just call crl.check_signature( root_pubkey )

         result.test_eq(test_name + " check_crl result",
                        Botan::Path_Validation_Result::status_string(Botan::PKIX::overall_status(crl_status)),
                        expected_result);
      } else if(end && root) {
         // CRT chain test

         Botan::Path_Validation_Restrictions restrictions(false, 80);  // SHA-1 is used

         Botan::Path_Validation_Result validation_result =
            Botan::x509_path_validate(*end, restrictions, store, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

         result.test_eq(test_name + " path validation result", validation_result.result_string(), expected_result);
      } else if(end && !root) {
         // CRT self signed test
         auto pubkey = end->subject_public_key();
         const bool accept = expected_result == "Verified";
         result.test_eq(test_name + " verify signature", end->check_signature(*pubkey), accept);
      } else if(csr) {
         // PKCS#10 Request test
         auto pubkey = csr->subject_public_key();
         const bool accept = expected_result == "Verified";
         result.test_eq(test_name + " verify signature", csr->check_signature(*pubkey), accept);
      }

      result.end_timer();
      results.push_back(result);
   }

   return results;
}

BOTAN_REGISTER_TEST("x509", "x509_path_rsa_pss", PSS_Path_Validation_Tests);

      #endif

class Validate_V1Cert_Test final : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> Validate_V1Cert_Test::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   const std::string root_crt = Test::data_file("x509/misc/v1ca/root.pem");
   const std::string int_crt = Test::data_file("x509/misc/v1ca/int.pem");
   const std::string ee_crt = Test::data_file("x509/misc/v1ca/ee.pem");

   auto validation_time = Botan::calendar_point(2019, 4, 19, 23, 0, 0).to_std_timepoint();

   Botan::X509_Certificate root(root_crt);
   Botan::X509_Certificate intermediate(int_crt);
   Botan::X509_Certificate ee_cert(ee_crt);

   Botan::Certificate_Store_In_Memory trusted;
   trusted.add_certificate(root);

   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};

   Botan::Path_Validation_Restrictions restrictions;
   Botan::Path_Validation_Result validation_result =
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

   Test::Result result("Verifying using v1 certificate");
   result.test_eq("Path validation result", validation_result.result_string(), "Verified");

   Botan::Certificate_Store_In_Memory empty;

   std::vector<Botan::X509_Certificate> new_chain = {ee_cert, intermediate, root};

   Botan::Path_Validation_Result validation_result2 =
      Botan::x509_path_validate(new_chain, restrictions, empty, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

   result.test_eq("Path validation result", validation_result2.result_string(), "Cannot establish trust");

   return {result};
}

BOTAN_REGISTER_TEST("x509", "x509_v1_ca", Validate_V1Cert_Test);

class Validate_V2Uid_in_V1_Test final : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> Validate_V2Uid_in_V1_Test::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   const std::string root_crt = Test::data_file("x509/v2-in-v1/root.pem");
   const std::string int_crt = Test::data_file("x509/v2-in-v1/int.pem");
   const std::string ee_crt = Test::data_file("x509/v2-in-v1/leaf.pem");

   auto validation_time = Botan::calendar_point(2020, 1, 1, 1, 0, 0).to_std_timepoint();

   Botan::X509_Certificate root(root_crt);
   Botan::X509_Certificate intermediate(int_crt);
   Botan::X509_Certificate ee_cert(ee_crt);

   Botan::Certificate_Store_In_Memory trusted;
   trusted.add_certificate(root);

   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};

   Botan::Path_Validation_Restrictions restrictions;
   Botan::Path_Validation_Result validation_result =
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

   Test::Result result("Verifying v1 certificate using v2 uid fields");
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
   result.test_eq(
      "Path validation result", validation_result.result_string(), "Encountered v2 identifiers in v1 certificate");

   return {result};
}

BOTAN_REGISTER_TEST("x509", "x509_v2uid_in_v1", Validate_V2Uid_in_V1_Test);

class Validate_Name_Constraint_SAN_Test final : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> Validate_Name_Constraint_SAN_Test::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   const std::string root_crt = Test::data_file("x509/name_constraint_san/root.pem");
   const std::string int_crt = Test::data_file("x509/name_constraint_san/int.pem");
   const std::string ee_crt = Test::data_file("x509/name_constraint_san/leaf.pem");

   auto validation_time = Botan::calendar_point(2020, 1, 1, 1, 0, 0).to_std_timepoint();

   Botan::X509_Certificate root(root_crt);
   Botan::X509_Certificate intermediate(int_crt);
   Botan::X509_Certificate ee_cert(ee_crt);

   Botan::Certificate_Store_In_Memory trusted;
   trusted.add_certificate(root);

   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};

   Botan::Path_Validation_Restrictions restrictions;
   Botan::Path_Validation_Result validation_result =
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

   Test::Result result("Verifying certificate with alternative SAN violating name constraint");
   result.test_eq("Path validation failed", validation_result.successful_validation(), false);
   result.test_eq(
      "Path validation result", validation_result.result_string(), "Certificate does not pass name constraint");

   return {result};
}

BOTAN_REGISTER_TEST("x509", "x509_name_constraint_san", Validate_Name_Constraint_SAN_Test);

class Validate_Name_Constraint_CaseInsensitive final : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> Validate_Name_Constraint_CaseInsensitive::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   const std::string root_crt = Test::data_file("x509/misc/name_constraint_ci/root.pem");
   const std::string int_crt = Test::data_file("x509/misc/name_constraint_ci/int.pem");
   const std::string ee_crt = Test::data_file("x509/misc/name_constraint_ci/leaf.pem");

   auto validation_time = Botan::calendar_point(2021, 5, 8, 1, 0, 0).to_std_timepoint();

   Botan::X509_Certificate root(root_crt);
   Botan::X509_Certificate intermediate(int_crt);
   Botan::X509_Certificate ee_cert(ee_crt);

   Botan::Certificate_Store_In_Memory trusted;
   trusted.add_certificate(root);

   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};

   Botan::Path_Validation_Restrictions restrictions;
   Botan::Path_Validation_Result validation_result =
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

   Test::Result result("DNS name constraints are case insensitive");
   result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);

   return {result};
}

BOTAN_REGISTER_TEST("x509", "x509_name_constraint_ci", Validate_Name_Constraint_CaseInsensitive);

class Validate_Name_Constraint_NoCheckSelf final : public Test {
   public:
      std::vector<Test::Result> run() override;
};

std::vector<Test::Result> Validate_Name_Constraint_NoCheckSelf::run() {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   const std::string root_crt = Test::data_file("x509/misc/nc_skip_self/root.pem");
   const std::string int_crt = Test::data_file("x509/misc/nc_skip_self/int.pem");
   const std::string ee_crt = Test::data_file("x509/misc/nc_skip_self/leaf.pem");

   auto validation_time = Botan::calendar_point(2021, 5, 8, 1, 0, 0).to_std_timepoint();

   Botan::X509_Certificate root(root_crt);
   Botan::X509_Certificate intermediate(int_crt);
   Botan::X509_Certificate ee_cert(ee_crt);

   Botan::Certificate_Store_In_Memory trusted;
   trusted.add_certificate(root);

   std::vector<Botan::X509_Certificate> chain = {ee_cert, intermediate};

   Botan::Path_Validation_Restrictions restrictions;
   Botan::Path_Validation_Result validation_result =
      Botan::x509_path_validate(chain, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

   Test::Result result("Name constraints do not apply to the certificate which includes them");
   result.test_eq("Path validation succeeded", validation_result.successful_validation(), true);

   return {result};
}

BOTAN_REGISTER_TEST("x509", "x509_name_constraint_no_check_self", Validate_Name_Constraint_NoCheckSelf);

class Root_Cert_Time_Check_Test final : public Test {
   public:
      std::vector<Test::Result> run() override {
         if(Botan::has_filesystem_impl() == false) {
            return {Test::Result::Note("Path validation", "Skipping due to missing filesystem access")};
         }

         const std::string trusted_root_crt = Test::data_file("x509/misc/root_cert_time_check/root.crt");
         const std::string leaf_crt = Test::data_file("x509/misc/root_cert_time_check/leaf.crt");

         const Botan::X509_Certificate trusted_root_cert(trusted_root_crt);
         const Botan::X509_Certificate leaf_cert(leaf_crt);

         Botan::Certificate_Store_In_Memory trusted;
         trusted.add_certificate(trusted_root_cert);

         const std::vector<Botan::X509_Certificate> chain = {leaf_cert};

         Test::Result result("Root cert time check");

         auto assert_path_validation_result = [&](std::string_view descr,
                                                  bool ignore_trusted_root_time_range,
                                                  uint32_t year,
                                                  Botan::Certificate_Status_Code exp_status,
                                                  std::optional<Botan::Certificate_Status_Code> exp_warning =
                                                     std::nullopt) {
            const Botan::Path_Validation_Restrictions restrictions(
               false,
               110,
               false,
               std::chrono::seconds::zero(),
               std::make_unique<Botan::Certificate_Store_In_Memory>(),
               ignore_trusted_root_time_range);

            const Botan::Path_Validation_Result validation_result =
               Botan::x509_path_validate(chain,
                                         restrictions,
                                         trusted,
                                         "",
                                         Botan::Usage_Type::UNSPECIFIED,
                                         Botan::calendar_point(year, 1, 1, 1, 0, 0).to_std_timepoint());
            const std::string descr_str = Botan::fmt(
               "Root cert validity range {}: {}", ignore_trusted_root_time_range ? "ignored" : "checked", descr);

            result.test_is_eq(descr_str, validation_result.result(), exp_status);
            const auto warnings = validation_result.warnings();
            BOTAN_ASSERT_NOMSG(warnings.size() == 2);
            result.confirm("No warning for leaf cert", warnings.at(0).empty());
            if(exp_warning) {
               result.confirm("Warning for root cert",
                              warnings.at(1).size() == 1 && warnings.at(1).contains(*exp_warning));
            } else {
               result.confirm("No warning for root cert", warnings.at(1).empty());
            }
         };
         // (Trusted) root cert validity range: 2022-2028
         // Leaf cert validity range: 2020-2030

         // Trusted root time range is checked
         assert_path_validation_result(
            "Root and leaf certs in validity range", false, 2025, Botan::Certificate_Status_Code::OK);
         assert_path_validation_result(
            "Root and leaf certs are expired", false, 2031, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
         assert_path_validation_result(
            "Root and leaf certs are not yet valid", false, 2019, Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);
         assert_path_validation_result(
            "Root cert is expired, leaf cert not", false, 2029, Botan::Certificate_Status_Code::CERT_HAS_EXPIRED);
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
                                       false,
                                       2021,
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID);

         // Trusted root time range is ignored
         assert_path_validation_result(
            "Root and leaf certs in validity range", true, 2025, Botan::Certificate_Status_Code::OK);
         assert_path_validation_result("Root and leaf certs are expired",
                                       true,
                                       2031,
                                       Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
         assert_path_validation_result("Root and leaf certs are not yet valid",
                                       true,
                                       2019,
                                       Botan::Certificate_Status_Code::CERT_NOT_YET_VALID,
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);
         assert_path_validation_result("Root cert is expired, leaf cert not",
                                       true,
                                       2029,
                                       Botan::Certificate_Status_Code::OK,
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_HAS_EXPIRED);
         assert_path_validation_result("Root cert is not yet valid, leaf cert is",
                                       true,
                                       2021,
                                       Botan::Certificate_Status_Code::OK,
                                       Botan::Certificate_Status_Code::TRUSTED_CERT_NOT_YET_VALID);

         return {result};
      }
};

BOTAN_REGISTER_TEST("x509", "x509_root_cert_time_check", Root_Cert_Time_Check_Test);

class Non_Self_Signed_Trust_Anchors_Test final : public Test {
   private:
      using Cert_Path = std::vector<Botan::X509_Certificate>;

      std::vector<Botan::X509_Certificate> get_valid_cert_chain() {
         // Test certs generated by https://github.com/yymax/x509test
         const std::string valid_chain_pem = "x509/x509test/ValidChained.pem";
         auto certs = load_cert_file(Test::data_file(valid_chain_pem));
         if(certs.size() != 5) {
            throw Test_Error(Botan::fmt("Failed to read all certs from {}", valid_chain_pem));
         }
         return certs;
      }

      /// Time point fits for all certificates used in this class
      std::chrono::system_clock::time_point get_validation_time() {
         return Botan::calendar_point(2016, 10, 21, 4, 20, 0).to_std_timepoint();
      }

      Botan::X509_Certificate get_self_signed_ee_cert() {
         const std::string valid_chain_pem = "x509/misc/self-signed-end-entity.pem";
         return Botan::X509_Certificate(Test::data_file(valid_chain_pem));
      }

      std::vector<Test::Result> path_validate_test() {
         std::vector<Test::Result> results;

         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
         const auto certs = get_valid_cert_chain();
         const auto validation_time = get_validation_time();

         const std::vector<std::tuple<std::string_view, Cert_Path, Botan::X509_Certificate>>
            info_w_end_certs_w_trust_anchor{
               {"length 1", Cert_Path{certs.at(0)}, certs.at(0)},
               {"length 2", Cert_Path{certs.at(0), certs.at(1)}, certs.at(1)},
               {"length 3 (store not in chain)", Cert_Path{certs.at(0), certs.at(1)}, certs.at(2)},
               {"length 4 (shuffled)", Cert_Path{certs.at(0), certs.at(3), certs.at(2), certs.at(1)}, certs.at(3)},
               {"full", Cert_Path{certs.at(0), certs.at(1), certs.at(2), certs.at(3), certs.at(4)}, certs.at(4)},
            };

         for(const auto& [info, end_certs, trust_anchor] : info_w_end_certs_w_trust_anchor) {
            Test::Result result(
               Botan::fmt("Non-self-signed trust anchor without require_self_signed_trust_anchors ({})", info));

            const Botan::Certificate_Store_In_Memory trusted(trust_anchor);

            auto path_result = Botan::x509_path_validate(
               end_certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

            if(path_result.successful_validation() && path_result.trust_root() != trust_anchor) {
               path_result = Botan::Path_Validation_Result(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);
            }

            result.test_eq(
               "path validation failed", path_result.result_string(), to_string(Botan::Certificate_Status_Code::OK));

            results.push_back(result);
         }
         return results;
      }

      std::vector<Test::Result> build_path_test() {
         std::vector<Test::Result> results;

         const auto restrictions = get_allow_non_self_signed_anchors_restrictions(false, false);
         const auto certs = get_valid_cert_chain();

         // Helper to create a cert path {certs[0],...,certs[last_cert]}
         const auto path_to = [&](size_t last_cert) -> Cert_Path {
            BOTAN_ASSERT_NOMSG(last_cert <= certs.size());
            return {certs.begin(), certs.begin() + last_cert + 1};
         };

         // Helper to create a cert store of all certificates in certs given by their indices
         const auto store_of = [&](auto... cert_indices) -> Botan::Certificate_Store_In_Memory {
            Botan::Certificate_Store_In_Memory cert_store;
            (cert_store.add_certificate(certs.at(cert_indices)), ...);
            return cert_store;
         };

         const std::vector<std::tuple<std::string, Botan::Certificate_Store_In_Memory, std::vector<Cert_Path>>>
            test_names_with_stores_with_expected_paths{
               {"root in store", store_of(4), std::vector<Cert_Path>{path_to(4)}},
               {"intermediate in store", store_of(2), std::vector<Cert_Path>{path_to(2)}},
               {"leaf in store", store_of(0), std::vector<Cert_Path>{path_to(0)}},
               {"leaf, intermediate, and root in store",
                store_of(0, 1, 4),
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(4)}},
               {"all in store",
                store_of(0, 1, 2, 3, 4),
                std::vector<Cert_Path>{path_to(0), path_to(1), path_to(2), path_to(3), path_to(4)}},
            };

         for(auto [test_name, cert_store, expected_paths] : test_names_with_stores_with_expected_paths) {
            Test::Result result(Botan::fmt("Build certificate paths ({})", test_name));

            std::vector<Cert_Path> cert_paths;
            const auto build_all_res =
               Botan::PKIX::build_all_certificate_paths(cert_paths, {&cert_store}, certs.at(0), certs);
            result.test_is_eq("build_all_certificate_paths result",
                              to_string(build_all_res),
                              to_string(Botan::Certificate_Status_Code::OK));
            result.test_is_eq("build_all_certificate_paths paths", cert_paths, expected_paths);

            Cert_Path cert_path;
            const auto build_path_res =
               Botan::PKIX::build_certificate_path(cert_path, {&cert_store}, certs.at(0), certs);
            result.test_is_eq("build_certificate_path result",
                              to_string(build_path_res),
                              to_string(Botan::Certificate_Status_Code::OK));

            if(std::ranges::find(cert_paths, path_to(4)) != cert_paths.end()) {
               result.test_is_eq("build_certificate_path (with self-signed anchor)", cert_path, path_to(4));
            } else {
               result.test_is_eq(
                  "build_certificate_path (without self-signed anchor)", cert_path, expected_paths.at(0));
            }
            results.push_back(result);
         }

         return results;
      }

      std::vector<Test::Result> forbidden_self_signed_trust_anchors_test() {
         const auto restrictions = get_default_restrictions(false, false);  // non-self-signed anchors are forbidden
         const auto certs = get_valid_cert_chain();
         const auto validation_time = get_validation_time();

         Botan::Certificate_Store_In_Memory cert_store(certs.at(3));

         Test::Result result("Build certificate paths with only non-self-signed trust anchors (forbidden)");

         const auto path_result = Botan::x509_path_validate(
            certs, restrictions, {&cert_store}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

         result.test_eq("unexpected path validation result",
                        path_result.result_string(),
                        to_string(Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST));

         const auto check_chain_result = Botan::PKIX::check_chain(
            {certs.at(0), certs.at(1), certs.at(2)}, validation_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions);

         result.test_ne("unexpected check_chain result",
                        Botan::Path_Validation_Result(check_chain_result, {}).result_string(),
                        to_string(Botan::Certificate_Status_Code::OK));

         return {result};
      }

      Test::Result stand_alone_root_test(std::string test_name,
                                         const Botan::Path_Validation_Restrictions& restrictions,
                                         const Botan::X509_Certificate& standalone_cert,
                                         Botan::Certificate_Status_Code expected_result) {
         Test::Result result(std::move(test_name));

         const auto validation_time = get_validation_time();
         Botan::Certificate_Store_In_Memory cert_store(standalone_cert);

         const auto path_result = Botan::x509_path_validate(
            standalone_cert, restrictions, {&cert_store}, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

         result.test_eq(
            "unexpected x509_path_validate result", path_result.result_string(), to_string(expected_result));

         return result;
      }

      std::vector<Test::Result> stand_alone_root_tests() {
         const auto self_signed_trust_anchor_forbidden = get_default_restrictions(false, false);
         const auto self_signed_trust_anchor_allowed = get_allow_non_self_signed_anchors_restrictions(false, false);

         const auto cert_chain = get_valid_cert_chain();
         const auto& self_signed_root = cert_chain.at(4);
         const auto& ica = cert_chain.at(3);
         const auto& self_signed_ee = get_self_signed_ee_cert();

         return {
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors forbidden)",
                                  self_signed_trust_anchor_forbidden,
                                  self_signed_root,
                                  Botan::Certificate_Status_Code::OK),
            stand_alone_root_test("Standalone self-signed root (non-self-signed trust anchors allowed)",
                                  self_signed_trust_anchor_allowed,
                                  self_signed_root,
                                  Botan::Certificate_Status_Code::OK),

            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors forbidden)",
                                  self_signed_trust_anchor_forbidden,
                                  self_signed_ee,
                                  Botan::Certificate_Status_Code::OK),
            stand_alone_root_test("Standalone self-signed end entity (non-self-signed trust anchors allowed)",
                                  self_signed_trust_anchor_allowed,
                                  self_signed_ee,
                                  Botan::Certificate_Status_Code::OK),

            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors forbidden)",
                                  self_signed_trust_anchor_forbidden,
                                  ica,
                                  Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST),
            stand_alone_root_test("Standalone intermediate certificate (non-self-signed trust anchors allowed)",
                                  self_signed_trust_anchor_allowed,
                                  ica,
                                  Botan::Certificate_Status_Code::OK),
         };
      }

   public:
      std::vector<Test::Result> run() override {
         std::vector<Test::Result> results;

         auto res = path_validate_test();
         results.insert(results.end(), res.begin(), res.end());

         res = build_path_test();
         results.insert(results.end(), res.begin(), res.end());

         res = forbidden_self_signed_trust_anchors_test();
         results.insert(results.end(), res.begin(), res.end());

         res = stand_alone_root_tests();
         results.insert(results.end(), res.begin(), res.end());

         return results;
      }
};

BOTAN_REGISTER_TEST("x509", "x509_non_self_signed_trust_anchors", Non_Self_Signed_Trust_Anchors_Test);

class BSI_Path_Validation_Tests final : public Test

{
   public:
      std::vector<Test::Result> run() override;

   private:
      std::vector<Test::Result> run_with_restrictions(const Botan::Path_Validation_Restrictions& restrictions);
};

std::vector<Test::Result> BSI_Path_Validation_Tests::run() {
   std::vector<Test::Result> results;
   for(const auto& restrictions : restrictions_to_test(false, false)) {
      auto partial_res = run_with_restrictions(restrictions);
      results.insert(results.end(), partial_res.begin(), partial_res.end());
   }
   return results;
}

std::vector<Test::Result> BSI_Path_Validation_Tests::run_with_restrictions(
   const Botan::Path_Validation_Restrictions& restriction_template) {
   if(Botan::has_filesystem_impl() == false) {
      return {Test::Result::Note("BSI path validation", "Skipping due to missing filesystem access")};
   }

   std::vector<Test::Result> results;

   for(const auto& [test_name, expected_result] : read_results(Test::data_file("x509/bsi/expected.txt"), '$')) {
      Test::Result result("BSI path validation");
      result.start_timer();

      const auto all_files = Test::files_in_data_dir("x509/bsi/" + test_name);

      Botan::Certificate_Store_In_Memory trusted;
      std::vector<Botan::X509_Certificate> certs;

      #if defined(BOTAN_HAS_MD5)
      const bool has_md5 = true;
      #else
      const bool has_md5 = false;
      #endif

      auto validation_time = Botan::calendar_point(2017, 8, 19, 12, 0, 0).to_std_timepoint();

      // By convention: if CRL is a substring if the test name,
      // we need to check the CRLs
      bool use_crl = false;
      if(test_name.find("CRL") != std::string::npos) {
         use_crl = true;
      }

      try {
         for(const auto& file : all_files) {
            // found a trust anchor
            if(file.find("TA") != std::string::npos) {
               trusted.add_certificate(Botan::X509_Certificate(file));
            }
            // found the target certificate. It needs to be at the front of certs
            else if(file.find("TC") != std::string::npos) {
               certs.insert(certs.begin(), Botan::X509_Certificate(file));
            }
            // found a certificate that might be part of a valid certificate chain to the trust anchor
            else if(file.find(".crt") != std::string::npos) {
               certs.push_back(Botan::X509_Certificate(file));
            } else if(file.find(".crl") != std::string::npos) {
               trusted.add_crl(Botan::X509_CRL(file));
            }
         }

         Botan::Path_Validation_Restrictions restrictions(use_crl,
                                                          restriction_template.minimum_key_strength(),
                                                          use_crl,
                                                          restriction_template.max_ocsp_age(),
                                                          std::make_unique<Botan::Certificate_Store_In_Memory>(),
                                                          restriction_template.ignore_trusted_root_time_range(),
                                                          restriction_template.require_self_signed_trust_anchors());

         /*
          * Following the test document, the test are executed 16 times with
          * randomly chosen order of the available certificates. However, the target
          * certificate needs to stay in front.
          * For certain test, the order in which the certificates are given to
          * the validation function may be relevant, i.e. if issuer DNs are
          * ambiguous.
          */
         class random_bit_generator {
            public:
               using result_type = size_t;

               static constexpr result_type min() { return 0; }

               static constexpr result_type max() { return std::numeric_limits<size_t>::max(); }

               result_type operator()() {
                  size_t s = 0;
                  m_rng.randomize(reinterpret_cast<uint8_t*>(&s), sizeof(s));
                  return s;
               }

               explicit random_bit_generator(Botan::RandomNumberGenerator& rng) : m_rng(rng) {}

            private:
               Botan::RandomNumberGenerator& m_rng;
         } rbg(this->rng());

         for(size_t r = 0; r < 16; r++) {
            std::shuffle(++(certs.begin()), certs.end(), rbg);

            Botan::Path_Validation_Result validation_result = Botan::x509_path_validate(
               certs, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, validation_time);

            // We expect to be warned
            if(expected_result.starts_with("Warning: ")) {
               std::string stripped = expected_result.substr(std::string("Warning: ").size());
               bool found_warning = false;
               for(const auto& warning_set : validation_result.warnings()) {
                  for(const auto& warning : warning_set) {
                     std::string warning_str(Botan::to_string(warning));
                     if(stripped == warning_str) {
                        result.test_eq(test_name + " path validation result", warning_str, stripped);
                        found_warning = true;
                     }
                  }
               }
               if(!found_warning) {
                  result.test_failure(test_name, "Did not receive the expected warning: " + stripped);
               }
            } else {
               if(expected_result == "Hash function used is considered too weak for security" && has_md5 == false) {
                  result.test_eq(test_name + " path validation result",
                                 validation_result.result_string(),
                                 "Certificate signed with unknown/unavailable algorithm");
               } else {
                  result.test_eq(
                     test_name + " path validation result", validation_result.result_string(), expected_result);
               }
            }
         }
      }

      /* Some certificates are rejected when executing the X509_Certificate constructor
       * by throwing a Decoding_Error exception.
       */
      catch(const Botan::Exception& e) {
         if(e.error_type() == Botan::ErrorType::DecodingFailure) {
            result.test_eq(test_name + " path validation result", e.what(), expected_result);
         } else {
            result.test_failure(test_name, e.what());
         }
      }

      result.end_timer();
      results.push_back(result);
   }

   return results;
}

BOTAN_REGISTER_TEST("x509", "x509_path_bsi", BSI_Path_Validation_Tests);

class Path_Validation_With_OCSP_Tests final : public Test {
   public:
      static Botan::X509_Certificate load_test_X509_cert(const std::string& path) {
         return Botan::X509_Certificate(Test::data_file(path));
      }

      static std::optional<Botan::OCSP::Response> load_test_OCSP_resp(const std::string& path) {
         return Botan::OCSP::Response(Test::read_binary_data_file(path));
      }

      static Test::Result validate_with_ocsp_with_next_update_without_max_age() {
         Test::Result result("path check with ocsp with next_update w/o max_age");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);

         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
         trusted.add_certificate(trust_root);

         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         std::optional<const Botan::OCSP::Response> ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");

         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
                               const Botan::Certificate_Status_Code expected) {
            const auto path_result = Botan::x509_path_validate(cert_path,
                                                               restrictions,
                                                               trusted,
                                                               "",
                                                               Botan::Usage_Type::UNSPECIFIED,
                                                               valid_time,
                                                               std::chrono::milliseconds(0),
                                                               {ocsp});

            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
                                     Botan::to_string(path_result.result()) + "'",
                                  path_result.result() == expected);
         };

         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);

         return result;
      }

      static Test::Result validate_with_ocsp_with_next_update_with_max_age() {
         Test::Result result("path check with ocsp with next_update with max_age");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::minutes(59));

         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
         trusted.add_certificate(trust_root);

         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         auto ocsp = load_test_OCSP_resp("x509/ocsp/randombit_ocsp.der");

         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
                               const Botan::Certificate_Status_Code expected) {
            const auto path_result = Botan::x509_path_validate(cert_path,
                                                               restrictions,
                                                               trusted,
                                                               "",
                                                               Botan::Usage_Type::UNSPECIFIED,
                                                               valid_time,
                                                               std::chrono::milliseconds(0),
                                                               {ocsp});

            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
                                     Botan::to_string(path_result.result()) + "'",
                                  path_result.result() == expected);
         };

         check_path(Botan::calendar_point(2016, 11, 11, 12, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
         check_path(Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2016, 11, 20, 8, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2016, 11, 28, 8, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);

         return result;
      }

      static Test::Result validate_with_ocsp_without_next_update_without_max_age() {
         Test::Result result("path check with ocsp w/o next_update w/o max_age");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false);

         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");

         trusted.add_certificate(trust_root);

         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");

         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
                               const Botan::Certificate_Status_Code expected) {
            const auto path_result = Botan::x509_path_validate(cert_path,
                                                               restrictions,
                                                               trusted,
                                                               "",
                                                               Botan::Usage_Type::UNSPECIFIED,
                                                               valid_time,
                                                               std::chrono::milliseconds(0),
                                                               {ocsp});

            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
                                     Botan::to_string(path_result.result()) + "'",
                                  path_result.result() == expected);
         };

         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(), Botan::Certificate_Status_Code::OK);

         return result;
      }

      static Test::Result validate_with_ocsp_without_next_update_with_max_age() {
         Test::Result result("path check with ocsp w/o next_update with max_age");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(false, 110, false, std::chrono::minutes(59));

         auto ee = load_test_X509_cert("x509/ocsp/patrickschmidt.pem");
         auto ca = load_test_X509_cert("x509/ocsp/bdrive_encryption.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/bdrive_root.pem");

         trusted.add_certificate(trust_root);

         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         auto ocsp = load_test_OCSP_resp("x509/ocsp/patrickschmidt_ocsp.der");

         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
                               const Botan::Certificate_Status_Code expected) {
            const auto path_result = Botan::x509_path_validate(cert_path,
                                                               restrictions,
                                                               trusted,
                                                               "",
                                                               Botan::Usage_Type::UNSPECIFIED,
                                                               valid_time,
                                                               std::chrono::milliseconds(0),
                                                               {ocsp});

            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
                                     Botan::to_string(path_result.result()) + "'",
                                  path_result.result() == expected);
         };

         check_path(Botan::calendar_point(2019, 5, 28, 7, 0, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
         check_path(Botan::calendar_point(2019, 5, 28, 7, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2019, 5, 28, 8, 0, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_IS_TOO_OLD);

         return result;
      }

      static Test::Result validate_with_ocsp_with_authorized_responder() {
         Test::Result result("path check with ocsp response from authorized responder certificate");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
                                                                 110,    // minimum key strength
                                                                 true);  // OCSP for all intermediates

         auto ee = load_test_X509_cert("x509/ocsp/bdr.pem");
         auto ca = load_test_X509_cert("x509/ocsp/bdr-int.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/bdr-root.pem");

         // These OCSP responses are signed by an authorized OCSP responder
         // certificate issued by `ca` and `trust_root` respectively. Note that
         // the responder certificates contain the "OCSP No Check" extension,
         // meaning that they themselves do not need a revocation check via OCSP.
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/bdr-ocsp-resp.der");
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/bdr-int-ocsp-resp.der");

         trusted.add_certificate(trust_root);
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
                               const Botan::Certificate_Status_Code expected) {
            const auto path_result = Botan::x509_path_validate(cert_path,
                                                               restrictions,
                                                               trusted,
                                                               "",
                                                               Botan::Usage_Type::UNSPECIFIED,
                                                               valid_time,
                                                               std::chrono::milliseconds(0),
                                                               {ocsp_ee, ocsp_ca});

            return result.confirm(std::string("Status: '") + Botan::to_string(expected) + "' should match '" +
                                     Botan::to_string(path_result.result()) + "'",
                                  path_result.result() == expected);
         };

         check_path(Botan::calendar_point(2022, 9, 18, 16, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_NOT_YET_VALID);
         check_path(Botan::calendar_point(2022, 9, 19, 16, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OK);
         check_path(Botan::calendar_point(2022, 9, 20, 16, 30, 0).to_std_timepoint(),
                    Botan::Certificate_Status_Code::OCSP_HAS_EXPIRED);

         return result;
      }

      static Test::Result validate_with_ocsp_with_authorized_responder_without_keyusage() {
         Test::Result result(
            "path check with ocsp response from authorized responder certificate (without sufficient key usage)");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
                                                                 110,     // minimum key strength
                                                                 false);  // OCSP for all intermediates

         // See `src/scripts/mychain_creater.sh` if you need to recreate those
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");

         auto ocsp_ee_delegate = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed.der").value();
         auto ocsp_ee_delegate_malformed =
            load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee_delegate_signed_malformed.der").value();

         trusted.add_certificate(trust_root);
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         auto check_path = [&](const std::chrono::system_clock::time_point valid_time,
                               const Botan::OCSP::Response& ocsp_ee,
                               const Botan::Certificate_Status_Code expected,
                               const std::optional<Botan::Certificate_Status_Code> also_expected = std::nullopt) {
            const auto path_result = Botan::x509_path_validate(cert_path,
                                                               restrictions,
                                                               trusted,
                                                               "",
                                                               Botan::Usage_Type::UNSPECIFIED,
                                                               valid_time,
                                                               std::chrono::milliseconds(0),
                                                               {ocsp_ee});

            result.test_is_eq("should result in expected validation status code",
                              static_cast<uint32_t>(path_result.result()),
                              static_cast<uint32_t>(expected));
            if(also_expected) {
               result.confirm("Secondary error is also present",
                              flatten(path_result.all_statuses()).contains(also_expected.value()));
            }
         };

         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
                    ocsp_ee_delegate,
                    Botan::Certificate_Status_Code::VERIFIED);
         check_path(Botan::calendar_point(2022, 10, 8, 23, 30, 0).to_std_timepoint(),
                    ocsp_ee_delegate,
                    Botan::Certificate_Status_Code::CERT_HAS_EXPIRED,
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
         check_path(Botan::calendar_point(2022, 9, 22, 23, 30, 0).to_std_timepoint(),
                    ocsp_ee_delegate_malformed,
                    Botan::Certificate_Status_Code::OCSP_RESPONSE_MISSING_KEYUSAGE,
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);

         return result;
      }

      static Test::Result validate_with_forged_ocsp_using_self_signed_cert() {
         Test::Result result("path check with forged ocsp using self-signed certificate");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(true,    // require revocation info
                                                                 110,     // minimum key strength
                                                                 false);  // OCSP for all intermediates

         auto ee = load_test_X509_cert("x509/ocsp/randombit.pem");
         auto ca = load_test_X509_cert("x509/ocsp/letsencrypt.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/identrust.pem");
         trusted.add_certificate(trust_root);

         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         auto check_path = [&](const std::string& forged_ocsp,
                               const Botan::Certificate_Status_Code expected,
                               const Botan::Certificate_Status_Code also_expected) {
            auto ocsp = load_test_OCSP_resp(forged_ocsp);
            const auto path_result =
               Botan::x509_path_validate(cert_path,
                                         restrictions,
                                         trusted,
                                         "",
                                         Botan::Usage_Type::UNSPECIFIED,
                                         Botan::calendar_point(2016, 11, 18, 12, 30, 0).to_std_timepoint(),
                                         std::chrono::milliseconds(0),
                                         {ocsp});

            result.test_is_eq(
               "Path validation with forged OCSP response should fail with", path_result.result(), expected);
            result.confirm("Secondary error is also present",
                           flatten(path_result.all_statuses()).contains(also_expected));
            result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));
         };

         // In both cases the path validation should detect the forged OCSP
         // response and generate an appropriate error. By no means it should
         // follow the unauthentic OCSP response.
         check_path("x509/ocsp/randombit_ocsp_forged_valid.der",
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);
         check_path("x509/ocsp/randombit_ocsp_forged_revoked.der",
                    Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND,
                    Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_TRUSTED);

         return result;
      }

      static Test::Result validate_with_ocsp_self_signed_by_intermediate_cert() {
         Test::Result result(
            "path check with ocsp response for intermediate that is (maliciously) self-signed by the intermediate");
         Botan::Certificate_Store_In_Memory trusted;

         auto restrictions = Botan::Path_Validation_Restrictions(true,   // require revocation info
                                                                 110,    // minimum key strength
                                                                 true);  // OCSP for all intermediates

         // See `src/scripts/mychain_creater.sh` if you need to recreate those
         auto ee = load_test_X509_cert("x509/ocsp/mychain_ee.pem");
         auto ca = load_test_X509_cert("x509/ocsp/mychain_int.pem");
         auto trust_root = load_test_X509_cert("x509/ocsp/mychain_root.pem");

         // this OCSP response for EE is valid (signed by intermediate cert)
         auto ocsp_ee = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_ee.der");

         // this OCSP response for Intermediate is malicious (signed by intermediate itself)
         auto ocsp_ca = load_test_OCSP_resp("x509/ocsp/mychain_ocsp_for_int_self_signed.der");

         trusted.add_certificate(trust_root);
         const std::vector<Botan::X509_Certificate> cert_path = {ee, ca, trust_root};

         const auto path_result =
            Botan::x509_path_validate(cert_path,
                                      restrictions,
                                      trusted,
                                      "",
                                      Botan::Usage_Type::UNSPECIFIED,
                                      Botan::calendar_point(2022, 9, 22, 22, 30, 0).to_std_timepoint(),
                                      std::chrono::milliseconds(0),
                                      {ocsp_ee, ocsp_ca});
         result.confirm("should reject intermediate OCSP response",
                        path_result.result() == Botan::Certificate_Status_Code::OCSP_ISSUER_NOT_FOUND);
         result.test_note(std::string("Failed with: ") + Botan::to_string(path_result.result()));

         return result;
      }

      std::vector<Test::Result> run() override {
         return {validate_with_ocsp_with_next_update_without_max_age(),
                 validate_with_ocsp_with_next_update_with_max_age(),
                 validate_with_ocsp_without_next_update_without_max_age(),
                 validate_with_ocsp_without_next_update_with_max_age(),
                 validate_with_ocsp_with_authorized_responder(),
                 validate_with_ocsp_with_authorized_responder_without_keyusage(),
                 validate_with_forged_ocsp_using_self_signed_cert(),
                 validate_with_ocsp_self_signed_by_intermediate_cert()};
      }
};

BOTAN_REGISTER_TEST("x509", "x509_path_with_ocsp", Path_Validation_With_OCSP_Tests);

   #endif

   #if defined(BOTAN_HAS_ECDSA)

class CVE_2020_0601_Tests final : public Test {
   public:
      std::vector<Test::Result> run() override {
         Test::Result result("CVE-2020-0601");

         if(!Botan::EC_Group::supports_application_specific_group()) {
            result.test_note("Skipping as application specific groups are not supported");
            return {result};
         }

         if(!Botan::EC_Group::supports_named_group("secp384r1")) {
            result.test_note("Skipping as secp384r1 is not supported");
            return {result};
         }

         const auto& secp384r1 = Botan::EC_Group::from_name("secp384r1");
         Botan::OID curveball_oid("1.3.6.1.4.1.25258.4.2020.0601");
         Botan::EC_Group curveball(
            curveball_oid,
            secp384r1.get_p(),
            secp384r1.get_a(),
            secp384r1.get_b(),
            BigInt(
               "0xC711162A761D568EBEB96265D4C3CEB4F0C330EC8F6DD76E39BCC849ABABB8E34378D581065DEFC77D9FCED6B39075DE"),
            BigInt(
               "0x0CB090DE23BAC8D13E67E019A91B86311E5F342DEE17FD15FB7E278A32A1EAC98FC97E18CB2F3B2C487A7DA6F40107AC"),
            secp384r1.get_order());

         auto ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ca.pem"));
         auto fake_ca_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/fake_ca.pem"));
         auto ee_crt = Botan::X509_Certificate(Test::data_file("x509/cve-2020-0601/ee.pem"));

         Botan::Certificate_Store_In_Memory trusted;
         trusted.add_certificate(ca_crt);

         const auto restrictions = Botan::Path_Validation_Restrictions(false, 80, false);

         const auto valid_time = Botan::calendar_point(2020, 1, 20, 0, 0, 0).to_std_timepoint();

         const auto path_result1 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt, fake_ca_crt},
                                                             restrictions,
                                                             trusted,
                                                             "",
                                                             Botan::Usage_Type::UNSPECIFIED,
                                                             valid_time,
                                                             std::chrono::milliseconds(0),
                                                             {});

         result.confirm("Validation failed", !path_result1.successful_validation());

         result.confirm("Expected status",
                        path_result1.result() == Botan::Certificate_Status_Code::CANNOT_ESTABLISH_TRUST);

         const auto path_result2 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
                                                             restrictions,
                                                             trusted,
                                                             "",
                                                             Botan::Usage_Type::UNSPECIFIED,
                                                             valid_time,
                                                             std::chrono::milliseconds(0),
                                                             {});

         result.confirm("Validation failed", !path_result2.successful_validation());

         result.confirm("Expected status",
                        path_result2.result() == Botan::Certificate_Status_Code::CERT_ISSUER_NOT_FOUND);

         // Verify the signature from the bad CA is actually correct
         Botan::Certificate_Store_In_Memory frusted;
         frusted.add_certificate(fake_ca_crt);

         const auto path_result3 = Botan::x509_path_validate(std::vector<Botan::X509_Certificate>{ee_crt},
                                                             restrictions,
                                                             frusted,
                                                             "",
                                                             Botan::Usage_Type::UNSPECIFIED,
                                                             valid_time,
                                                             std::chrono::milliseconds(0),
                                                             {});

         result.confirm("Validation succeeded", path_result3.successful_validation());

         return {result};
      }
};

BOTAN_REGISTER_TEST("x509", "x509_cve_2020_0601", CVE_2020_0601_Tests);

class Path_Validation_With_Immortal_CRL final : public Test {
   public:
      std::vector<Test::Result> run() override {
         // RFC 5280 defines the nextUpdate field as "optional" (in line with
         // the original X.509 standard), but then requires all conforming CAs
         // to always define it. For best compatibility we must deal with both.
         Test::Result result("Using a CRL without a nextUpdate field");

         if(Botan::has_filesystem_impl() == false) {
            result.test_note("Skipping due to missing filesystem access");
            return {result};
         }

         Botan::X509_Certificate root(Test::data_file("x509/misc/crl_without_nextupdate/ca.pem"));
         Botan::X509_Certificate revoked_subject(Test::data_file("x509/misc/crl_without_nextupdate/01.pem"));
         Botan::X509_Certificate valid_subject(Test::data_file("x509/misc/crl_without_nextupdate/42.pem"));

         // Check that a CRL without nextUpdate is parsable
         auto crl = Botan::X509_CRL(Test::data_file("x509/misc/crl_without_nextupdate/valid_forever.crl"));
         result.confirm("this update is set", crl.this_update().time_is_set());
         result.confirm("next update is not set", !crl.next_update().time_is_set());
         result.confirm("CRL is not empty", !crl.get_revoked().empty());

         // Ensure that we support the used sig algo, otherwish stop here
         if(!Botan::EC_Group::supports_named_group("brainpool512r1")) {
            result.test_note("Cannot test path validation because signature algorithm is not support in this build");
            return {result};
         }

         Botan::Certificate_Store_In_Memory trusted;
         trusted.add_certificate(root);
         trusted.add_crl(crl);

         // Just before the CA and subject certificates expire
         // (validity from 01 March 2025 to 24 February 2026)
         auto valid_time = Botan::calendar_point(2026, 2, 23, 0, 0, 0).to_std_timepoint();

         Botan::Path_Validation_Restrictions restrictions(true /* require revocation info */);

         // Validate a certificate that is not listed in the CRL
         const auto valid = Botan::x509_path_validate(
            valid_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
         if(!result.confirm("Valid certificate", valid.successful_validation())) {
            result.test_note(valid.result_string());
         }

         // Ensure that a certificate listed in the CRL is recognized as revoked
         const auto revoked = Botan::x509_path_validate(
            revoked_subject, restrictions, trusted, "", Botan::Usage_Type::UNSPECIFIED, valid_time);
         if(!result.confirm("No valid certificate", !revoked.successful_validation())) {
            result.test_note(revoked.result_string());
         }
         result.test_is_eq("Certificate is revoked", revoked.result(), Botan::Certificate_Status_Code::CERT_IS_REVOKED);

         return {result};
      }
};

BOTAN_REGISTER_TEST("x509", "x509_path_immortal_crl", Path_Validation_With_Immortal_CRL);

   #endif

   #if defined(BOTAN_HAS_XMSS_RFC8391)

class XMSS_Path_Validation_Tests final : public Test {
   public:
      static Test::Result validate_self_signed(const std::string& name, const std::string& file) {
         Test::Result result(name);

         Botan::Path_Validation_Restrictions restrictions;
         auto self_signed = Botan::X509_Certificate(Test::data_file("x509/xmss/" + file));

         auto cert_path = std::vector<Botan::X509_Certificate>{self_signed};
         auto valid_time = Botan::calendar_point(2019, 10, 8, 4, 45, 0).to_std_timepoint();

         auto status = Botan::PKIX::overall_status(
            Botan::PKIX::check_chain(cert_path, valid_time, "", Botan::Usage_Type::UNSPECIFIED, restrictions));
         result.test_eq("Cert validation status", Botan::to_string(status), "Verified");
         return result;
      }

      std::vector<Test::Result> run() override {
         if(Botan::has_filesystem_impl() == false) {
            return {Test::Result::Note("XMSS path validation", "Skipping due to missing filesystem access")};
         }

         return {
            validate_self_signed("XMSS path validation with certificate created by ISARA corp", "xmss_isara_root.pem"),
            validate_self_signed("XMSS path validation with certificate created by BouncyCastle",
                                 "xmss_bouncycastle_sha256_10_root.pem")};
      }
};

BOTAN_REGISTER_TEST("x509", "x509_path_xmss", XMSS_Path_Validation_Tests);

   #endif

#endif

}  // namespace

}  // namespace Botan_Tests
